package cn.wjssl.infrastructure.persistent.repository;

import cn.bugstack.middleware.db.router.strategy.IDBRouterStrategy;
import cn.wjssl.domain.activity.event.ActivitySkuStockZeroMessageEvent;
import cn.wjssl.domain.activity.model.aggregate.CreatePartakeOrderAggregate;
import cn.wjssl.domain.activity.model.aggregate.CreateQuotaOrderAggregate;
import cn.wjssl.domain.activity.model.entity.*;
import cn.wjssl.domain.activity.model.valobj.ActivitySkuStockKeyVO;
import cn.wjssl.domain.activity.model.valobj.ActivityStateVO;
import cn.wjssl.domain.activity.model.valobj.UserRaffleOrderStateVO;
import cn.wjssl.domain.activity.repository.IActivityRepository;
import cn.wjssl.infrastructure.event.EventPublisher;
import cn.wjssl.infrastructure.persistent.dao.*;
import cn.wjssl.infrastructure.persistent.po.*;
import cn.wjssl.infrastructure.persistent.redis.IRedisService;
import cn.wjssl.types.common.Constants;
import cn.wjssl.types.enums.ResponseCode;
import cn.wjssl.types.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RLock;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 活动仓储实现类
 */
@Slf4j
@Repository
public class ActivityRepository implements IActivityRepository {

    // 通过redis查询
    @Resource
    private IRedisService redisService;
    // 通过Mysql查询
    @Resource
    private IRaffleActivitySkuDao skuDao;
    @Resource
    private IRaffleActivityDao activityDao;
    @Resource
    private IRaffleActivityCountDao countDao;
    @Resource
    private IRaffleActivityOrderDao activityOrderDao;
    @Resource
    private IRaffleActivityAccountDao accountDao;
    @Resource
    private IUserRaffleOrderDao userOrderDao;
    @Resource
    private IRaffleActivityAccountDayDao accountDayDao;
    @Resource
    private IRaffleActivityAccountMonthDao accountMonthDao;
    @Resource
    private IUserCreditAccountDao creditAccountDao;

    // DBRouter
    @Resource
    private IDBRouterStrategy dbRouter;
    // 事务
    @Resource
    private TransactionTemplate transactionTemplate;
    // MQ
    @Resource
    private EventPublisher eventPublisher;
    // 活动扣减消息标准格式
    @Resource
    private ActivitySkuStockZeroMessageEvent activitySkuStockZeroMessageEvent;

    /**
     * 根据sku 查询活动商品sku实体
     * 如果缓存中的sku的库存为0, 或者不存在, 就设置当前sku的库存为0
     * 在后续 活动的基础校验过滤器中, 抛出异常
     * @param sku
     * @return
     */
    @Override
    public ActivitySkuEntity queryActivitySku(Long sku) {
        RaffleActivitySkuPO skuPO = skuDao.queryActivitySku(sku);
        String cacheKey = Constants.RedisKey.ACTIVITY_SKU_STOCK_COUNT_KEY + sku;
        Long cacheSkuStock = redisService.getAtomicLong(cacheKey);
        if (null == cacheSkuStock || 0 == cacheSkuStock) {
            cacheSkuStock = 0L;
        }
        return ActivitySkuEntity.builder()
                .sku(skuPO.getSku())
                .activityId(skuPO.getActivityId())
                .activityCountId(skuPO.getActivityCountId())
                .stockCount(skuPO.getStockCount())
                .stockCountSurplus(cacheSkuStock.intValue())
                .productAmount(skuPO.getProductAmount())
                .build();
    }

    // 根据活动id, 查询活动实体, 优先走redis
    @Override
    public ActivityEntity queryRaffleActivityByActivityId(Long activityId) {
        // 优先从缓存获取
        String cacheKey = Constants.RedisKey.ACTIVITY_KEY + activityId;
        ActivityEntity activityEntity = redisService.getValue(cacheKey);
        if (null != activityEntity) return activityEntity;
        // 从库中获取数据
        RaffleActivityPO raffleActivityPO = activityDao.queryRaffleActivityByActivityId(activityId);
        activityEntity = ActivityEntity.builder()
                .activityId(raffleActivityPO.getActivityId())
                .activityName(raffleActivityPO.getActivityName())
                .activityDesc(raffleActivityPO.getActivityDesc())
                .beginDateTime(raffleActivityPO.getBeginDateTime())
                .endDateTime(raffleActivityPO.getEndDateTime())
                .strategyId(raffleActivityPO.getStrategyId())
                .state(ActivityStateVO.valueOf(raffleActivityPO.getState()))
                .build();
        // 没有就存入redis
        redisService.setValue(cacheKey, activityEntity);
        return activityEntity;
    }

    // 根据活动次数, 查询活动次数实体, 优先走redis
    @Override
    public ActivityCountEntity queryRaffleActivityCountByActivityCountId(Long activityCountId) {
        // 优先从缓存获取
        String cacheKey = Constants.RedisKey.ACTIVITY_COUNT_KEY + activityCountId;
        ActivityCountEntity activityCountEntity = redisService.getValue(cacheKey);
        if (null != activityCountEntity) return activityCountEntity;
        // 从库中获取数据
        RaffleActivityCountPO raffleActivityCountPO = countDao.queryRaffleActivityCountByActivityCountId(activityCountId);
        activityCountEntity = ActivityCountEntity.builder()
                .activityCountId(raffleActivityCountPO.getActivityCountId())
                .totalCount(raffleActivityCountPO.getTotalCount())
                .dayCount(raffleActivityCountPO.getDayCount())
                .monthCount(raffleActivityCountPO.getMonthCount())
                .build();
        // 没有就存入redis
        redisService.setValue(cacheKey, activityCountEntity);
        return activityCountEntity;
    }

    /**
     * 下sku订单, 增加用户活动账户抽奖次数
     * 可以直接在方法上使用注解, 但这里为了手动处理异常, 对重复订单的异常进行捕获和处理, 所以通过手动事务声明
     * @Transactional(rollbackFor = Exception.class)
     * 通过事务, 将订单聚合对象拆分, 更新activity_order表和account表
     * @param createOrderAggregate
     *
     * 对 userId,
     */
    @Override
    public void doSaveSkuOrder(CreateQuotaOrderAggregate createOrderAggregate) {
        RLock lock = redisService.getLock(Constants.RedisKey.ACTIVITY_ACCOUNT_LOCK
                + createOrderAggregate.getUserId()
                + Constants.UNDERLINE
                + createOrderAggregate.getActivityId()
                + Constants.UNDERLINE
                + createOrderAggregate.getActivityOrderEntity().getOrderId()
        );
        try {
            lock.lock(3, TimeUnit.SECONDS);
            // 获取订单对象
            ActivityOrderEntity activityOrderEntity = createOrderAggregate.getActivityOrderEntity();
            // 构建raffle_activity_order表PO
            RaffleActivityOrderPO orderPO = new RaffleActivityOrderPO();
            orderPO.setUserId(activityOrderEntity.getUserId());
            orderPO.setSku(activityOrderEntity.getSku());
            orderPO.setActivityId(activityOrderEntity.getActivityId());
            orderPO.setActivityName(activityOrderEntity.getActivityName());
            orderPO.setStrategyId(activityOrderEntity.getStrategyId());
            orderPO.setOrderId(activityOrderEntity.getOrderId());
            orderPO.setOrderTime(activityOrderEntity.getOrderTime());
            orderPO.setTotalCount(activityOrderEntity.getTotalCount());
            orderPO.setDayCount(activityOrderEntity.getDayCount());
            orderPO.setMonthCount(activityOrderEntity.getMonthCount());
            orderPO.setTotalCount(createOrderAggregate.getTotalCount());
            orderPO.setDayCount(createOrderAggregate.getDayCount());
            orderPO.setMonthCount(createOrderAggregate.getMonthCount());
            orderPO.setState(activityOrderEntity.getState().getCode());
            orderPO.setOutBusinessNo(activityOrderEntity.getOutBusinessNo());

            // 构建总账户 account表PO
            RaffleActivityAccountPO accountPO = new RaffleActivityAccountPO();
            accountPO.setUserId(createOrderAggregate.getUserId());
            accountPO.setActivityId(createOrderAggregate.getActivityId());
            accountPO.setTotalCount(createOrderAggregate.getTotalCount());
            accountPO.setTotalCountSurplus(createOrderAggregate.getTotalCount());
            accountPO.setDayCount(createOrderAggregate.getDayCount());
            accountPO.setDayCountSurplus(createOrderAggregate.getDayCount());
            accountPO.setMonthCount(createOrderAggregate.getMonthCount());
            accountPO.setMonthCountSurplus(createOrderAggregate.getMonthCount());

            // 构建日账户 account_day表PO
            RaffleActivityAccountDayPO accountDayPO = new RaffleActivityAccountDayPO();
            accountDayPO.setUserId(createOrderAggregate.getUserId());
            accountDayPO.setActivityId(createOrderAggregate.getActivityId());
            accountDayPO.setDay(accountDayPO.currentDay());
            accountDayPO.setDayCount(createOrderAggregate.getDayCount());
            accountDayPO.setDayCountSurplus(createOrderAggregate.getDayCount());

            // 构建月账户 account_month表PO
            RaffleActivityAccountMonthPO accountMonthPO = new RaffleActivityAccountMonthPO();
            accountMonthPO.setUserId(createOrderAggregate.getUserId());
            accountMonthPO.setActivityId(createOrderAggregate.getActivityId());
            accountMonthPO.setMonth(accountMonthPO.currentMonth());
            accountMonthPO.setMonthCount(createOrderAggregate.getMonthCount());
            accountMonthPO.setMonthCountSurplus(createOrderAggregate.getMonthCount());

            // 以用户ID作为切分键，通过 IDBRouterStrategy接口 设定路由【这样就保证了下面的操作，都是同一个链接下，也就保证了事务的特性】
            dbRouter.doRouter(createOrderAggregate.getUserId());
            // 使用事务
            transactionTemplate.execute(status -> {
                try {
                    //  写入订单, order分库分表
                    activityOrderDao.insert(orderPO);
                    /**
                     * 先查询有没有账户
                     * 如果没有账户就创建账户
                     * 如果有账户, 就增加抽奖次数
                     */
                    RaffleActivityAccountPO accountPORes = accountDao.queryAccount(accountPO);
                    if (null == accountPORes) {
                        accountDao.insert(accountPO);
                    } else {
                        accountDao.updateAccountQuota(accountPO);
                    }
                    // 更新日账户和月账户
                    accountDayDao.addAccountQuota(accountDayPO);
                    accountMonthDao.addAccountQuota(accountMonthPO);
                    // 如果更新能成功就返回1, 如果更新不成功那就会捕获重复下单异常
                    return 1;
                }catch (DuplicateKeyException e) {      // 唯一键冲突异常
                    status.setRollbackOnly();   // 手动回滚事务
                    log.error("写入sku订单记录，唯一索引冲突 userId: {} activityId: {} sku: {}", activityOrderEntity.getUserId(), activityOrderEntity.getActivityId(), activityOrderEntity.getSku(), e);
                    throw new AppException(ResponseCode.INDEX_DUP.getCode());
                }
            });
        } finally {
            //  清空当前线程中的路由值
            dbRouter.clear();
            lock.unlock();
        }
    }

    /**
     * 存入无需支付的sku单, 因为是返利, 直接更新用户账户的抽奖次数
     * @param createQuotaOrderAggregate
     */
    @Override
    public void doSaveNoPayOrder(CreateQuotaOrderAggregate createQuotaOrderAggregate) {
        RLock lock = redisService.getLock(Constants.RedisKey.ACTIVITY_ACCOUNT_LOCK
                + createQuotaOrderAggregate.getUserId()
                + Constants.UNDERLINE
                + createQuotaOrderAggregate.getActivityId()
                + Constants.UNDERLINE
                + createQuotaOrderAggregate.getActivityOrderEntity().getOrderId()
        );
        try {
            lock.lock(3, TimeUnit.SECONDS);
            dbRouter.doRouter(createQuotaOrderAggregate.getUserId());
            /**
             * 1. 构建sku单对象
             * 2. 构建总账户对象
             * 3. 构建月账户对象
             * 4. 构建日账户对象
             */
            // sku单
            ActivityOrderEntity orderEntity = createQuotaOrderAggregate.getActivityOrderEntity();
            RaffleActivityOrderPO orderPO = new RaffleActivityOrderPO();
            orderPO.setSku(orderEntity.getSku());
            orderPO.setUserId(orderEntity.getUserId());
            orderPO.setActivityId(orderEntity.getActivityId());
            orderPO.setActivityName(orderEntity.getActivityName());
            orderPO.setStrategyId(orderEntity.getStrategyId());
            orderPO.setOrderId(orderEntity.getOrderId());
            orderPO.setOrderTime(orderEntity.getOrderTime());
            orderPO.setTotalCount(orderEntity.getTotalCount());
            orderPO.setDayCount(orderEntity.getDayCount());
            orderPO.setMonthCount(orderEntity.getMonthCount());
            orderPO.setPayAmount(orderEntity.getPayAmount());
            orderPO.setState(orderEntity.getState().getCode());
            orderPO.setOutBusinessNo(orderEntity.getOutBusinessNo());

            // 账户对象 - 总
            RaffleActivityAccountPO accountPO = new RaffleActivityAccountPO();
            accountPO.setUserId(createQuotaOrderAggregate.getUserId());
            accountPO.setActivityId(createQuotaOrderAggregate.getActivityId());
            accountPO.setTotalCount(createQuotaOrderAggregate.getTotalCount());
            accountPO.setTotalCountSurplus(createQuotaOrderAggregate.getTotalCount());
            accountPO.setDayCount(createQuotaOrderAggregate.getDayCount());
            accountPO.setDayCountSurplus(createQuotaOrderAggregate.getDayCount());
            accountPO.setMonthCount(createQuotaOrderAggregate.getMonthCount());
            accountPO.setMonthCountSurplus(createQuotaOrderAggregate.getMonthCount());

            // 账户对象 - 月
            RaffleActivityAccountMonthPO accountMonthPO = new RaffleActivityAccountMonthPO();
            accountMonthPO.setUserId(createQuotaOrderAggregate.getUserId());
            accountMonthPO.setActivityId(createQuotaOrderAggregate.getActivityId());
            accountMonthPO.setMonth(accountMonthPO.currentMonth());
            accountMonthPO.setMonthCount(createQuotaOrderAggregate.getMonthCount());
            accountMonthPO.setMonthCountSurplus(createQuotaOrderAggregate.getMonthCount());

            // 账户对象 - 日
            RaffleActivityAccountDayPO accountDayPO = new RaffleActivityAccountDayPO();
            accountDayPO.setUserId(createQuotaOrderAggregate.getUserId());
            accountDayPO.setActivityId(createQuotaOrderAggregate.getActivityId());
            accountDayPO.setDay(accountDayPO.currentDay());
            accountDayPO.setDayCount(createQuotaOrderAggregate.getDayCount());
            accountDayPO.setDayCountSurplus(createQuotaOrderAggregate.getDayCount());


            // 开启事务, 插入sku单(completed), 并增加用户日, 月, 总账户的抽奖次数
            transactionTemplate.execute(status -> {
                try {
                    // 1. 插入sku单
                    activityOrderDao.insert(orderPO);
                    // 2. 更新总, 日, 月账户
                    RaffleActivityAccountPO accountPORes = accountDao.queryAccount(accountPO);
                    if (null == accountPORes) {
                        accountDao.insert(accountPO);
                    } else {
                        accountDao.updateAccountQuota(accountPO);
                    }
                    accountDayDao.addAccountQuota(accountDayPO);
                    accountMonthDao.addAccountQuota(accountMonthPO);
                    return 1;
                }catch (DuplicateKeyException e) {
                    status.setRollbackOnly();
                    log.error("写入订单记录，唯一索引冲突 userId: {} activityId: {} sku: {}", orderEntity.getUserId(), orderEntity.getActivityId(), orderEntity.getSku(), e);
                    throw new AppException(ResponseCode.INDEX_DUP.getCode(), e);
                }
            });
        }finally {
            dbRouter.clear();
            lock.unlock();
        }
    }

    /**
     * 存入需要支付的sku单, 这里因为需要等待支付完成才会更新用户账户, 所以只做sku单的插入
     * @param createQuotaOrderAggregate
     */
    @Override
    public void doSaveCreditPayOrder(CreateQuotaOrderAggregate createQuotaOrderAggregate) {
        try {
            dbRouter.doRouter(createQuotaOrderAggregate.getUserId());
            // 构建订单实体
            // sku单
            ActivityOrderEntity orderEntity = createQuotaOrderAggregate.getActivityOrderEntity();
            RaffleActivityOrderPO orderPO = new RaffleActivityOrderPO();
            orderPO.setSku(orderEntity.getSku());
            orderPO.setUserId(orderEntity.getUserId());
            orderPO.setActivityId(orderEntity.getActivityId());
            orderPO.setActivityName(orderEntity.getActivityName());
            orderPO.setStrategyId(orderEntity.getStrategyId());
            orderPO.setOrderId(orderEntity.getOrderId());
            orderPO.setOrderTime(orderEntity.getOrderTime());
            orderPO.setTotalCount(orderEntity.getTotalCount());
            orderPO.setDayCount(orderEntity.getDayCount());
            orderPO.setMonthCount(orderEntity.getMonthCount());
            orderPO.setPayAmount(orderEntity.getPayAmount());
            orderPO.setState(orderEntity.getState().getCode());
            orderPO.setOutBusinessNo(orderEntity.getOutBusinessNo());
            // 事务, 插入sku单
            transactionTemplate.execute(status -> {
                try {
                    activityOrderDao.insert(orderPO);
                    return 1;
                } catch (DuplicateKeyException e) {
                    status.setRollbackOnly();
                    log.error("写入订单记录，唯一索引冲突 userId: {} activityId: {} sku: {}", orderEntity.getUserId(), orderEntity.getActivityId(), orderEntity.getSku(), e);
                    throw new AppException(ResponseCode.INDEX_DUP.getCode(), e);
                }
            });
        }finally {
            dbRouter.clear();
        }
    }

    // 缓存活动SKU初始库存, Constants.RedisKey.ACTIVITY_SKU_STOCK_COUNT_KEY + sku;
    @Override
    public void cacheActivitySkuStockCount(String cacheKey, Integer stockCount) {
        //if (redisService.isExists(cacheKey)) return;
        redisService.setAtomicLong(cacheKey, stockCount);
    }

    // 扣减库存, Constants.RedisKey.ACTIVITY_SKU_STOCK_COUNT_KEY + sku;
    @Override
    public boolean subtractionActivitySkuStock(Long sku, String cacheKey, Date endDateTime) {
        // 库存-1
        long surplus = redisService.decr(cacheKey);
        // 如果库存为0, 那就给mq发消息, 告诉现在库存已经消耗为0, 让mq的监听器去同步数据库,并且清空redis的延迟队列
        if (surplus == 0) {
            // 发送消息
            eventPublisher.publish(
                    activitySkuStockZeroMessageEvent.topic(),
                    activitySkuStockZeroMessageEvent.buildEventMessage(sku)
            );
            // 扣减失败
            return false;
        }else if (surplus < 0){
            // 库存小于0，恢复为0个
            redisService.setAtomicLong(cacheKey, 0);
            return false;
        }
        // 1. 按照cacheKey decr 后的值，如 99、98、97 和 key 组成为库存锁的key进行使用。
        // 2. 加锁为了兜底，如果后续有恢复库存，手动处理等【运营是人来操作，会有这种情况发放，系统要做防护】，也不会超卖。因为所有的可用库存key，都被加锁了。
        // 3. 设置加锁时间为活动到期 + 延迟1天
        // key = Constants.RedisKey.ACTIVITY_SKU_STOCK_COUNT_KEY + sku + _ + 99
        String lockKey = cacheKey + Constants.UNDERLINE + surplus;
        long expireMillis = endDateTime.getTime() - System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1);
        Boolean lock = redisService.setNx(lockKey, expireMillis, TimeUnit.MILLISECONDS);
        if (!lock) {
            log.info("活动sku库存加锁失败 {}", lockKey);
        }
        return lock;
    }

    // 把扣减库存对应的SKU放入redis延迟队列, 等待定时任务消费
    @Override
    public void activitySkuStockConsumeSendQueue(ActivitySkuStockKeyVO activitySkuStockKeyVO) {
        // 修改, 给每个sku创建对应的阻塞队列
        String cacheKey = Constants.RedisKey.ACTIVITY_SKU_COUNT_QUEUE_KEY + Constants.UNDERLINE + activitySkuStockKeyVO.getSku();
        RBlockingQueue<ActivitySkuStockKeyVO> blockingQueue = redisService.getBlockingQueue(cacheKey);
        RDelayedQueue<ActivitySkuStockKeyVO> delayedQueue = redisService.getDelayedQueue(blockingQueue);
        delayedQueue.offer(activitySkuStockKeyVO, 3, TimeUnit.SECONDS);
    }

    // 弹出sku的redis队列中的第一个, 用于同步消耗数据库库存
    @Override
    public ActivitySkuStockKeyVO takeQueueValue() {
        String cacheKey = Constants.RedisKey.ACTIVITY_SKU_COUNT_QUEUE_KEY;
        RBlockingQueue<ActivitySkuStockKeyVO> blockingQueue = redisService.getBlockingQueue(cacheKey);
        return blockingQueue.poll();
    }

    @Override
    public ActivitySkuStockKeyVO takeQueueValue(Long sku) {
        String cacheKey = Constants.RedisKey.ACTIVITY_SKU_COUNT_QUEUE_KEY + Constants.UNDERLINE + sku;
        RBlockingQueue<ActivitySkuStockKeyVO> blockingQueue = redisService.getBlockingQueue(cacheKey);
        return blockingQueue.poll();
    }

    // 同步更新Mysql中的库存
    @Override
    public void updateActivitySkuStock(Long sku) {
        skuDao.updateActivitySkuStock(sku);
    }

    // 缓存库存以消耗完毕，直接更新数据库库存为0
    @Override
    public void clearActivitySkuStock(Long sku) {
        skuDao.clearActivitySkuStock(sku);
    }

    // 清空redis的延迟队列
    @Override
    public void clearQueueValue() {
        String cacheKey = Constants.RedisKey.ACTIVITY_SKU_COUNT_QUEUE_KEY;
        RBlockingQueue<ActivitySkuStockKeyVO> blockingQueue = redisService.getBlockingQueue(cacheKey);
        blockingQueue.clear();
    }

    // 查询未使用的活动订单
    @Override
    public UserRaffleOrderEntity queryNoUsedRaffleOrder(PartakeRaffleActivityEntity partakeRaffleActivityEntity) {

        UserRaffleOrderPO userRaffleOrderPOReq = new UserRaffleOrderPO();
        userRaffleOrderPOReq.setUserId(partakeRaffleActivityEntity.getUserId());
        userRaffleOrderPOReq.setActivityId(partakeRaffleActivityEntity.getActivityId());
        // 这里判断的是 userId, activity, state是create, 而不是used
        UserRaffleOrderPO userRaffleOrderPORes = userOrderDao.queryNoUsedRaffleOrder(userRaffleOrderPOReq);
        // 如果没有记录, 那相当于连订单都没有, 那就直接返回null
        if (userRaffleOrderPORes == null) return null;
        // 如果有记录, 那就拼接用户订单实体,返回
        UserRaffleOrderEntity userRaffleOrderEntity = new UserRaffleOrderEntity();
        userRaffleOrderEntity.setUserId(userRaffleOrderPORes.getUserId());
        userRaffleOrderEntity.setActivityId(userRaffleOrderPORes.getActivityId());
        userRaffleOrderEntity.setActivityName(userRaffleOrderPORes.getActivityName());
        userRaffleOrderEntity.setStrategyId(userRaffleOrderPORes.getStrategyId());
        userRaffleOrderEntity.setOrderId(userRaffleOrderPORes.getOrderId());
        userRaffleOrderEntity.setOrderTime(userRaffleOrderPORes.getOrderTime());
        userRaffleOrderEntity.setOrderState(UserRaffleOrderStateVO.valueOf(userRaffleOrderPORes.getOrderState()));
        return userRaffleOrderEntity;
    }

    /**
     * 通过事务, 将聚合对象拆分, 更新user_order表, account表, account_day表, account_month表
     * 因为使用分表, 所以需要try dbRouter
     * 下抽奖单, 扣减活动账户抽奖次数, 对 userId, activityId, orderId 加锁, 保证幂等性, 防止重复下单扣减抽奖次数
     * @param createPartakeOrderAggregate
     */
    @Override
    public void saveCreatePartakeOrderAggregate(CreatePartakeOrderAggregate createPartakeOrderAggregate) {
        RLock lock = redisService.getLock(Constants.RedisKey.ACTIVITY_ACCOUNT_LOCK
                + createPartakeOrderAggregate.getUserId()
                + Constants.UNDERLINE
                + createPartakeOrderAggregate.getActivityId()
                + Constants.UNDERLINE
                + createPartakeOrderAggregate.getUserRaffleOrderEntity().getOrderId()
        );
        try {
            // 加锁
            lock.lock(3, TimeUnit.SECONDS);
            String userId = createPartakeOrderAggregate.getUserId();
            Long activityId = createPartakeOrderAggregate.getActivityId();
            ActivityAccountEntity activityAccountEntity = createPartakeOrderAggregate.getActivityAccountEntity();
            ActivityAccountMonthEntity activityAccountMonthEntity = createPartakeOrderAggregate.getActivityAccountMonthEntity();
            ActivityAccountDayEntity activityAccountDayEntity = createPartakeOrderAggregate.getActivityAccountDayEntity();
            UserRaffleOrderEntity userRaffleOrderEntity = createPartakeOrderAggregate.getUserRaffleOrderEntity();

            dbRouter.doRouter(userId);
            transactionTemplate.execute(status -> {
                try {
                    // 扣减总账户库存
                    int accountI = accountDao.updateActivityAccountSubtractionQuota(
                            RaffleActivityAccountPO.builder()
                                    .userId(userId)
                                    .activityId(activityId)
                                    .build());
                    // 如果没有扣减成功, 报异常
                    if (1 != accountI) {
                        status.setRollbackOnly();
                        log.warn("写入创建参与活动记录，更新总账户额度不足，异常 userId: {} activityId: {}", userId, activityId);
                        throw new AppException(ResponseCode.ACCOUNT_QUOTA_ERROR.getCode(), ResponseCode.ACCOUNT_QUOTA_ERROR.getInfo());
                    }

                    // 更新月账户, 如果存在月账户, 直接通过 activityId , userId 和 month 进行查询扣减
                    if (createPartakeOrderAggregate.isExistAccountMonth()) {    // 存在月账户
                        int accountMonthI = accountMonthDao.updateActivityAccountMonthSubtractionQuota(
                                RaffleActivityAccountMonthPO.builder()
                                        .userId(userId)
                                        .activityId(activityId)
                                        // yyyy-mm
                                        .month(activityAccountMonthEntity.getMonth())
                                        .build());
                        if (1 != accountMonthI) {   // 月账户扣减失败
                            status.setRollbackOnly();
                            log.warn("写入创建参与活动记录，更新月账户额度不足，异常 userId: {} activityId: {} month: {}", userId, activityId, activityAccountMonthEntity.getMonth());
                            throw new AppException(ResponseCode.ACCOUNT_MONTH_QUOTA_ERROR.getCode(), ResponseCode.ACCOUNT_MONTH_QUOTA_ERROR.getInfo());
                        }
                    }else {     // 没有月账户, 就创建一个月账户
                        accountMonthDao.insertActivityAccountMonth(RaffleActivityAccountMonthPO.builder()
                                .userId(activityAccountMonthEntity.getUserId())
                                .activityId(activityAccountMonthEntity.getActivityId())
                                .month(activityAccountMonthEntity.getMonth())
                                .monthCount(activityAccountMonthEntity.getMonthCount())
                                .monthCountSurplus(activityAccountMonthEntity.getMonthCountSurplus() - 1)
                                .build());
                        // 新创建月账户，则更新总账表中月镜像额度
                        accountDao.updateActivityAccountMonthSurplusImageQuota(RaffleActivityAccountPO.builder()
                                .userId(userId)
                                .activityId(activityId)
                                .monthCountSurplus(activityAccountEntity.getMonthCountSurplus())
                                .build());
                    }

                    // 更新日账户, 如果存在日账户, 直接通过 activityId , userId 和 day 进行查询扣减
                    if (createPartakeOrderAggregate.isExistAccountDay()) {  // 存在日账户
                        int accountDayI = accountDayDao.updateActivityAccountDaySubtractionQuota(RaffleActivityAccountDayPO.builder()
                                .userId(userId)
                                .activityId(activityId)
                                // yyyy-mm-dd
                                .day(activityAccountDayEntity.getDay())
                                .build());
                        if (1 != accountDayI) {     // 日账户扣减失败
                            // 未更新成功则回滚
                            status.setRollbackOnly();
                            log.warn("写入创建参与活动记录，更新日账户额度不足，异常 userId: {} activityId: {} day: {}", userId, activityId, activityAccountDayEntity.getDay());
                            throw new AppException(ResponseCode.ACCOUNT_DAY_QUOTA_ERROR.getCode(), ResponseCode.ACCOUNT_DAY_QUOTA_ERROR.getInfo());
                        }
                    } else {    // 如果没有日账户, 就创建一个日账户
                        accountDayDao.insertActivityAccountDay(RaffleActivityAccountDayPO.builder()
                                .userId(activityAccountDayEntity.getUserId())
                                .activityId(activityAccountDayEntity.getActivityId())
                                .day(activityAccountDayEntity.getDay())
                                .dayCount(activityAccountDayEntity.getDayCount())
                                .dayCountSurplus(activityAccountDayEntity.getDayCountSurplus() - 1)
                                .build());
                        // 新创建日账户，则更新总账表中日镜像额度
                        accountDao.updateActivityAccountDaySurplusImageQuota(RaffleActivityAccountPO.builder()
                                .userId(userId)
                                .activityId(activityId)
                                .dayCountSurplus(activityAccountEntity.getDayCountSurplus())
                                .build());
                    }

                    // 如果都成功, 插入订单
                    userOrderDao.insertUserOrder(UserRaffleOrderPO.builder()
                            .userId(userRaffleOrderEntity.getUserId())
                            .activityId(userRaffleOrderEntity.getActivityId())
                            .activityName(userRaffleOrderEntity.getActivityName())
                            .strategyId(userRaffleOrderEntity.getStrategyId())
                            .orderId(userRaffleOrderEntity.getOrderId())
                            .orderTime(userRaffleOrderEntity.getOrderTime())
                            .orderState(userRaffleOrderEntity.getOrderState().getCode())
                            .build());

                    return 1;
                } catch (DuplicateKeyException e) {
                    status.setRollbackOnly();
                    log.error("写入创建参与活动记录，唯一索引冲突 userId: {} activityId: {}", userId, activityId, e);
                    throw new AppException(ResponseCode.INDEX_DUP.getCode(), e);
                }
            });
        }finally {
            dbRouter.clear();
            lock.unlock();  // 释放锁
        }
    }

    // 根据 userId, activityId 查询账户
    @Override
    public ActivityAccountEntity queryActivityAccountByUserId(String userId, Long activityId) {
        RaffleActivityAccountPO raffleActivityAccountPOReq = new RaffleActivityAccountPO();
        raffleActivityAccountPOReq.setUserId(userId);
        raffleActivityAccountPOReq.setActivityId(activityId);
        RaffleActivityAccountPO raffleActivityAccountRes = accountDao.queryActivityAccountByUserId(raffleActivityAccountPOReq);
        if (null == raffleActivityAccountRes) return null;
        // 2. 转换对象
        return ActivityAccountEntity.builder()
                .userId(raffleActivityAccountRes.getUserId())
                .activityId(raffleActivityAccountRes.getActivityId())
                .totalCount(raffleActivityAccountRes.getTotalCount())
                .totalCountSurplus(raffleActivityAccountRes.getTotalCountSurplus())
                .dayCount(raffleActivityAccountRes.getDayCount())
                .dayCountSurplus(raffleActivityAccountRes.getDayCountSurplus())
                .monthCount(raffleActivityAccountRes.getMonthCount())
                .monthCountSurplus(raffleActivityAccountRes.getMonthCountSurplus())
                .build();
    }

    // 根据userId, activityId, month 查询 accountMonth
    @Override
    public ActivityAccountMonthEntity queryActivityAccountMonthByUserId(String userId, Long activityId, String month) {
        RaffleActivityAccountMonthPO raffleActivityAccountMonthPOReq = new RaffleActivityAccountMonthPO();
        raffleActivityAccountMonthPOReq.setUserId(userId);
        raffleActivityAccountMonthPOReq.setActivityId(activityId);
        raffleActivityAccountMonthPOReq.setMonth(month);
        RaffleActivityAccountMonthPO raffleActivityAccountMonthPORes = accountMonthDao.queryActivityAccountMonthByUserId(raffleActivityAccountMonthPOReq);
        if (null == raffleActivityAccountMonthPORes) return null;
        // 2. 转换对象
        return ActivityAccountMonthEntity.builder()
                .userId(raffleActivityAccountMonthPORes.getUserId())
                .activityId(raffleActivityAccountMonthPORes.getActivityId())
                .month(raffleActivityAccountMonthPORes.getMonth())
                .monthCount(raffleActivityAccountMonthPORes.getMonthCount())
                .monthCountSurplus(raffleActivityAccountMonthPORes.getMonthCountSurplus())
                .build();
    }

    // 根据userId, activityId, day 查询 accountDay
    @Override
    public ActivityAccountDayEntity queryActivityAccountDayByUserId(String userId, Long activityId, String day) {
        RaffleActivityAccountDayPO raffleActivityAccountDayPOReq = new RaffleActivityAccountDayPO();
        raffleActivityAccountDayPOReq.setUserId(userId);
        raffleActivityAccountDayPOReq.setActivityId(activityId);
        raffleActivityAccountDayPOReq.setDay(day);
        RaffleActivityAccountDayPO raffleActivityAccountDayPORes = accountDayDao.queryActivityAccountDayByUserId(raffleActivityAccountDayPOReq);
        if (null == raffleActivityAccountDayPORes) return null;
        // 2. 转换对象
        return ActivityAccountDayEntity.builder()
                .userId(raffleActivityAccountDayPORes.getUserId())
                .activityId(raffleActivityAccountDayPORes.getActivityId())
                .day(raffleActivityAccountDayPORes.getDay())
                .dayCount(raffleActivityAccountDayPORes.getDayCount())
                .dayCountSurplus(raffleActivityAccountDayPORes.getDayCountSurplus())
                .build();
    }

    // 根据activityId, 查询sku表中的商品sku列表
    @Override
    public List<ActivitySkuEntity> queryActivitySkuListByActivityId(Long activityId) {
        List<RaffleActivitySkuPO> skuPOS = skuDao.queryActivitySkuListByActivityId(activityId);
        List<ActivitySkuEntity> skuEntities = new ArrayList<>(skuPOS.size());
        for (RaffleActivitySkuPO skuPO : skuPOS) {
            ActivitySkuEntity activitySkuEntity = new ActivitySkuEntity();
            activitySkuEntity.setSku(skuPO.getSku());
            activitySkuEntity.setActivityCountId(skuPO.getActivityCountId());
            activitySkuEntity.setStockCount(skuPO.getStockCount());
            activitySkuEntity.setStockCountSurplus(skuPO.getStockCountSurplus());
            skuEntities.add(activitySkuEntity);
        }
        return skuEntities;
    }

    // 根据activityId, userId 查询account_day表 相减获得用户已抽奖次数, 如果没有账户就返回 0
    @Override
    public Integer queryRaffleActivityAccountDayPartakeCount(Long activityId, String userId) {
        RaffleActivityAccountDayPO accountDayPO = new RaffleActivityAccountDayPO();
        accountDayPO.setActivityId(activityId);
        accountDayPO.setUserId(userId);
        accountDayPO.setDay(accountDayPO.currentDay());
        Integer dayPartakeCount = accountDayDao.queryRaffleActivityAccountDayPartakeCount(accountDayPO);
        // 当日未参与抽奖则为0次
        return null == dayPartakeCount ? 0 : dayPartakeCount;
    }

    // 根据activityId, userId 查询account表 获取用户账户实体
    @Override
    public ActivityAccountEntity queryActivityAccountEntity(Long activityId, String userId, String day, String month) {
        // 1. 查询总账户
        RaffleActivityAccountPO accountPO = accountDao.queryActivityAccountByUserId(RaffleActivityAccountPO.builder()
                .activityId(activityId)
                .userId(userId)
                .build()
        );

        // 2. 如果没有总账户, 那就返回都是0
        if (null == accountPO) {
            return ActivityAccountEntity.builder()
                    .activityId(activityId)
                    .userId(userId)
                    .totalCount(0)
                    .totalCountSurplus(0)
                    .monthCount(0)
                    .monthCountSurplus(0)
                    .dayCount(0)
                    .dayCountSurplus(0)
                    .build();
        }

        // 通过月账户, 日账户, 组装当前实际的账户对象
        // 3. 用activityId, userId 查询月账户
        RaffleActivityAccountMonthPO accountMonthPO = accountMonthDao.queryActivityAccountMonthByUserId(RaffleActivityAccountMonthPO.builder()
                .activityId(activityId)
                .userId(userId)
                .month(month)
                .build()
        );

        // 4. 用activityId, userId 查询日账户
        RaffleActivityAccountDayPO accountDayPO = accountDayDao.queryActivityAccountDayByUserId(RaffleActivityAccountDayPO.builder()
                .activityId(activityId)
                .userId(userId)
                .day(day)
                .build()
        );

        // 5. 组装结果
        ActivityAccountEntity res = new ActivityAccountEntity();
        res.setActivityId(activityId);
        res.setUserId(userId);
        res.setTotalCount(accountPO.getTotalCount());
        res.setTotalCountSurplus(accountPO.getTotalCountSurplus());

        // 先从日账户和月账户中获取日库存和月库存, 如果没有的话再去总账户中获取
        if (accountMonthPO != null) {
            res.setMonthCount(accountMonthPO.getMonthCount());
            res.setMonthCountSurplus(accountMonthPO.getMonthCountSurplus());
        } else {
            res.setMonthCount(accountPO.getMonthCount());
            res.setMonthCountSurplus(accountPO.getMonthCountSurplus());
        }

        if (accountDayPO != null) {
            res.setDayCount(accountDayPO.getDayCount());
            res.setDayCountSurplus(accountDayPO.getDayCountSurplus());
        } else {
            res.setDayCount(accountPO.getDayCount());
            res.setDayCountSurplus(accountPO.getDayCountSurplus());
        }

        // 5. 返回结果
        return res;
    }

    // 查询用户 [当前总共] 抽奖次数, 如果没有账户就返回 0
    @Override
    public Integer queryRaffleActivityAccountTotalPartakeCount(Long activityId, String userId) {
        RaffleActivityAccountPO accountPO = accountDao.queryActivityAccountByUserId(RaffleActivityAccountPO.builder()
                .activityId(activityId)
                .userId(userId)
                .build()
        );
        if (null == accountPO) return 0;
        return accountPO.getTotalCount() - accountPO.getTotalCountSurplus();
    }

    // 通过userId, outBusinessNo, 查询sku单, 更新sku单, 并更新总账户, 月账户, 日账户
    @Override
    public void updateOrder(DeliveryOrderEntity deliveryOrderEntity) {
        RLock lock = redisService.getLock(Constants.RedisKey.ACTIVITY_ACCOUNT_UPDATE_LOCK
                + deliveryOrderEntity.getUserId()
        );
        try {
            lock.lock(3, TimeUnit.SECONDS);
            // 1. 查询sku单
            RaffleActivityOrderPO orderPO = new RaffleActivityOrderPO();
            orderPO.setUserId(deliveryOrderEntity.getUserId());
            orderPO.setOutBusinessNo(deliveryOrderEntity.getOutBusinessNo());
            RaffleActivityOrderPO orderPORes = activityOrderDao.queryRaffleActivityOrder(orderPO);

            // 如果差不到该笔订单, 那就直接解锁
            if (null == orderPORes) {
                if (lock.isLocked()) lock.unlock();
                return;
            }

            // 2. 总账户, 日账户, 月账户
            // 账户对象 - 总
            RaffleActivityAccountPO accountPO = new RaffleActivityAccountPO();
            accountPO.setUserId(orderPORes.getUserId());
            accountPO.setActivityId(orderPORes.getActivityId());
            accountPO.setTotalCount(orderPORes.getTotalCount());
            accountPO.setTotalCountSurplus(orderPORes.getTotalCount());
            accountPO.setDayCount(orderPORes.getDayCount());
            accountPO.setDayCountSurplus(orderPORes.getDayCount());
            accountPO.setMonthCount(orderPORes.getMonthCount());
            accountPO.setMonthCountSurplus(orderPORes.getMonthCount());

            // 账户对象 - 月
            RaffleActivityAccountMonthPO accountMonthPO = new RaffleActivityAccountMonthPO();
            accountMonthPO.setUserId(orderPORes.getUserId());
            accountMonthPO.setActivityId(orderPORes.getActivityId());
            accountMonthPO.setMonth(accountMonthPO.currentMonth());
            accountMonthPO.setMonthCount(orderPORes.getMonthCount());
            accountMonthPO.setMonthCountSurplus(orderPORes.getMonthCount());

            // 账户对象 - 日
            RaffleActivityAccountDayPO accountDayPO = new RaffleActivityAccountDayPO();
            accountDayPO.setUserId(orderPORes.getUserId());
            accountDayPO.setActivityId(orderPORes.getActivityId());
            accountDayPO.setDay(accountDayPO.currentDay());
            accountDayPO.setDayCount(orderPORes.getDayCount());
            accountDayPO.setDayCountSurplus(orderPORes.getDayCount());

            // 事务更新sku单, 并更新总账户, 月账户, 日账户
            dbRouter.doRouter(deliveryOrderEntity.getUserId());
            transactionTemplate.execute(status -> {
                try {
                    // 1. 更新sku单为completed，如果签到的sku单，那么就重新插入
                    int i = activityOrderDao.updateOrderCompleted(orderPORes);
                    if (1 != i) {
                        status.setRollbackOnly();
                        return 1;
                    }

                    // 2. 更新总账户
                    RaffleActivityAccountPO accountPORes = accountDao.queryAccount(accountPO);
                    if (accountPORes == null) {
                        accountDao.insert(accountPO);
                    } else {
                        accountDao.updateAccountQuota(accountPO);
                    }
                    // 3. 更新日,月账户
                    accountMonthDao.addAccountQuota(accountMonthPO);
                    accountDayDao.addAccountQuota(accountDayPO);
                    return 1;
                }catch (DuplicateKeyException e) {
                    status.setRollbackOnly();
                    log.error("更新订单记录，完成态，唯一索引冲突 userId: {} outBusinessNo: {}", deliveryOrderEntity.getUserId(), deliveryOrderEntity.getOutBusinessNo(), e);
                    throw new AppException(ResponseCode.INDEX_DUP.getCode(), e);
                }
            });

        }finally {
            dbRouter.clear();
            lock.unlock();
        }
    }

    /**
     * 根据activityId 查询sku表, 获取sku商品列表
     * @param activityId
     * @return
     */
    @Override
    public List<SkuProductEntity> querySkuProductEntityListByActivityId(Long activityId) {
        List<RaffleActivitySkuPO> skuPOList = skuDao.queryActivitySkuListByActivityId(activityId);
        List<SkuProductEntity> resList = new ArrayList<>(skuPOList.size());
        for (RaffleActivitySkuPO skuPO : skuPOList) {
            RaffleActivityCountPO countPO = countDao.queryRaffleActivityCountByActivityCountId(skuPO.getActivityCountId());

            SkuProductEntity.ActivityCount skuCount = new SkuProductEntity.ActivityCount();
            skuCount.setTotalCount(countPO.getTotalCount());
            skuCount.setMonthCount(countPO.getMonthCount());
            skuCount.setDayCount(countPO.getDayCount());

            resList.add(SkuProductEntity.builder()
                    .sku(skuPO.getSku())
                    .activityId(skuPO.getActivityId())
                    .activityCountId(skuPO.getActivityCountId())
                    .stockCount(skuPO.getStockCount())
                    .stockCountSurplus(skuPO.getStockCountSurplus())
                    .productAmount(skuPO.getProductAmount())
                    .activityCount(skuCount)
                    .build());
        }
        return resList;
    }

    //  根据 userId, 查询积分账户余额
    @Override
    public BigDecimal queryUserCreditAccountAmount(String userId) {
        UserCreditAccountPO accountPO = new UserCreditAccountPO();
        accountPO.setUserId(userId);
        UserCreditAccountPO creditAccountPORes = creditAccountDao.queryUserCreditAccountByUserId(accountPO);
        if (null == creditAccountPORes) return BigDecimal.ZERO;
        return creditAccountPORes.getAvailableAmount();
    }

    // 查询所有sku商品的sku编号
    @Override
    public List<Long> querySkuList() {
        return skuDao.querySkuList();
    }
}
